package http

import (
	"io"
	"log"
	"net"
	"net/http"
	"time"
)

func HttpProxy(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodConnect {
		log.Printf("Proxy request: %s %s %s\n", r.Method, r.Host, r.Proto)
		handleHttps(w, r)
	} else {
		handleHttp(w, r)
	}
}

func handleHttps(w http.ResponseWriter, r *http.Request) {
	dstConn, err := net.DialTimeout("tcp", r.Host, time.Minute)
	if err != nil {
		log.Printf("Dial error: %s\n", err.Error())
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	w.WriteHeader(http.StatusOK)

	hijacker, ok := w.(http.Hijacker)
	if !ok {
		log.Println("Hijacking not supported")
		http.Error(w, "Hijacking not supported", http.StatusBadGateway)
		return
	}

	clientConn, _, err := hijacker.Hijack()
	if err != nil {
		log.Printf("Hijack error: %s\n", err.Error())
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}

	go transfer(dstConn, clientConn, "Server")
	go transfer(clientConn, dstConn, "Client")
}

func handleHttp(w http.ResponseWriter, r *http.Request) {
	resp, err := http.DefaultTransport.RoundTrip(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	defer resp.Body.Close()
	copyHeader(w.Header(), r.Header)
	w.WriteHeader(resp.StatusCode)
	io.Copy(w, resp.Body)
}

func transfer(dst io.WriteCloser, src io.ReadCloser, direction string) {
	if direction == "Client" {
		defer dst.Close()
		defer src.Close()
	}
	n, err := io.Copy(dst, src)
	if err != nil {
		log.Printf("Transfer %s error: %s\n", direction, err.Error())
	} else {
		log.Printf("Transfer %s %d bytes\n", direction, n)
	}
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}
